<h1>Keyboard Lock</h1>

<pre class="metadata">
Shortname: keyboard-lock
Level:
Group: webplatform
Status: CG-DRAFT
ED: https://wicg.github.io/keyboard-lock/
Repository: w3c/keyboard-lock
Previous Version:
	<none>
Editor:
    Gary Kacmarcik, Google, garykac@google.com
    Jamie Walch, Google, jamiewalch@google.com
Abstract:
	This specification defines an API that allows websites to
	capture keys that are normally reserved by the underlying host
	operating system. It is intended to be used by web
	applications that provide a fullscreen immersive experience
	(like games or remote access apps).

Status Text:
	This document is an editor's draft proposed as a First Public Working Draft.
	
</pre>

<pre class="anchors">
urlPrefix: http://www.w3.org/TR/uievents-code/#; type: dfn; spec: uievents-code;
	text: key code attribute value
urlPrefix: https://fullscreen.spec.whatwg.org/#; type: dfn; spec: fullscreen;
	text: fullscreen element
urlPrefix: https://html.spec.whatwg.org/#; type: dfn; spec: html;
	text: currently focused area of a top-level browsing context
	text: top-level browsing context
urlPrefix: http://www.w3.org/TR/uievents-code/#code-; type: dfn; spec: uievents-code;
	text: Delete
	text: KeyA
	text: KeyD
	text: KeyS
	text: KeyW
spec: ecma-262; urlPrefix: http://tc39.github.io/ecma262/
    type: dfn
        text: promise; url: sec-promise-objects
</pre>

<pre class="biblio">
{
	"QuartzEventServices": {
		"title": "Quartz Event Services",
		"href": "https://developer.apple.com/reference/coregraphics/1658572-quartz_event_services"
	},
	"GrabKeyboard": {
		"title": "X11 GrabKeyboard API",
		"href": "https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#requests:GrabKeyboard"
	},
    "LowLevelKeyboardProc": {
		"title": "LowLevelKeyboardProc documentation on MSDN",
		"href": "https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx"
	}
}
</pre>

<h2 id="introduction">Introduction</h2>

	Richly interactive web sites, games and remote
	desktop/application streaming experiences want to provide an
	immersive, full screen experience. To accomplish this, sites
	need access to special keys and keyboard shortcuts while they
	are in full screen mode so that they can be used for
	navigation, menus or gaming functionality. Some examples of
	the keys that may be required are Escape, Alt+Tab, Cmd+`, and
	Ctrl+N.

	By default, these keys are not available to the web application because
	they are captured by the browser or the underlying operating
	system. The Keyboard Lock API enables websites to capture and use
	all available keys allowed by the OS.

<h2 id="API">Keyboard Lock API</h2>

	<h3 id="navigator-interface">Navigator Interface</h3>

		<pre class="idl" data-highlight="webidl">
		partial interface Navigator {
			[SecureContext, SameObject] readonly attribute Keyboard keyboard;
		};
		</pre>

		<div id="navigator-idl" dfn-for="Navigator">

		<div class="algorithm" data-algorithm="navigator-keyboard">
		<h4 id="h-navigator-keyboard"><dfn>keyboard</h4>
		The [=keyboard=] attribute must return the {{Navigator}}'s {{Keyboard}} object.
		</div>

		</div><!-- dfn-for Navigator -->

		Note: The [=keyboard=] object is also defined in the 
		<a href="https://wicg.github.io/keyboard-map/#navigator-interface">Keyboard Map</a>
		specification. These two definitions will be reconciled once we decide on a final
		home for these specifications.
		
	<h3 id="keyboard-interface">Keyboard Interface</h3>

		<pre class="idl" data-highlight="webidl">
		[SecureContext, Exposed=Window] interface Keyboard {
			Promise&lt;void> lock(optional sequence&lt;DOMString> keyCodes = []);
			void unlock();
		};
		</pre>

		<div id="keyboard-idl" dfn-for="Keyboard">

		The keyboard object has <dfn>enable keyboard lock</dfn>, which is a
		boolean that is set to true when Keyboard Lock is enabled.
		By default, this is set to false.

		The keyboard object has <dfn>reserved key codes</dfn>, which is a
		set of DOMStrings, each of which is a valid [=key code attribute value=]
		as defined in [[UIEvents-Code]].
		By default this set is empty (which would capture all keys
		if [=enable keyboard lock=] was enabled).

		The keyboard object has a <dfn>keyboard lock task queue</dfn> which is
		initialized to the result of [=starting a new parallel queue=].
		
		<div class="algorithm" data-algorithm="keyboard-lock">
		<h4 id="h-keyboard-lock"><dfn>lock()</dfn></h4>

			When {{lock()}} is called, the user agent must
			run the following steps:

			1. Let |p| be a new [=Promise=].
		
			1. If not currently executing in the currently active [=top-level browsing context=], then
		
				1. Reject |p| with an "{{InvalidStateError}}" {{DOMException}}.

			1. [=Enqueue the following steps=] to the [=keyboard lock task queue=]:

				1. Reset [=reserved key codes=] to be an empty set.

				1. If the optional {{keyCodes}} argument is present, run the
					following substeps:

					1. [=list/For each=] string |key| in {{keyCodes}}:
			
						1. If |key| is not a valid [=key code attribute value=], then
				
							1. Set [=enable keyboard lock=] to be false.

							1. Reject |p| with an "{{InvalidAccessError}}" {{DOMException}}.

						1. [=set/Append=] |key| to [=reserved key codes=].
				
				1. If [=enable keyboard lock=] is currently false, run the following
					substeps:

					1. Optionally, [=register a system key press handler=].

					1. Set [=enable keyboard lock=] to be true.

				1. If there is a pending {{lock()}} task in the [=keyboard lock task queue=], then
		
					1. Set [=enable keyboard lock=] to be false.

					1. Reject the |p| with an "{{AbortError}}" DOMException.
						
				1. Resolve |p|.

			1. Return |p|.

			<div></div>

			Note: If {{lock()}} is called multiple times without an
			intervening call to {{unlock()}}, then only the
			{{keyCodes}} specified in the last request call will be in effect.
			If a second call to {{lock()}} is made before the first one has finished,
			then the first one will be rejected with "{{AbortError}}".
		
			<div class="example">
			To capture all keys, simply call {{lock()}} with no arguments:
			<pre>
				navigator.keyboard.lock();
			</pre>
			</div>
		
			<div class="example">
			To capture the "W", "A", "S", and "D" keys, call {{lock()}} with
			a list that contains the [=key code attribute value=] for each of these keys:
			<pre>
				navigator.keyboard.lock(["KeyW", "KeyA", "KeyS", "KeyD"]);
			</pre>
			This will capture these keys regardless of which modifiers are used with the
			key press. Assuming a standard US QWERTY layout, registering [=KeyW=] will
			ensure that "W", Shift+"W", Control+"W", Control+Shift+"W", and all other key
			modifier combinations with "W" will be sent to the app. Similarly for
			[=KeyA=], [=KeyS=] and [=KeyD=].
			</div>
		
			<div class="example">
			Note that requesting a key is not a guarantee that all modified versions will
			be made available to the app. As an example, consider [=Delete=]
			<pre>
				navigator.keyboard.lock(["Delete"]);
			</pre>
			While this will make most Delete key presses available (e.g., Shift+Delete,
			Control+Delete, Shift+Control+Delete), on Windows it will not make available
			the “secure attention sequence” Control+Alt+Delete.
			</div>
		
		</div><!-- lock() -->
		
		<div class="algorithm" data-algorithm="keyboard-unlock">
		<h4 id="h-keyboard-unlock"><dfn>unlock()</dfn></h4>

			When {{unlock()}} is called, the user agent must
			run the following steps:

			1. [=Enqueue the following steps=] to the [=keyboard lock task queue=]:

				1. If [=enable keyboard lock=] is true, then run the following substeps:

					1. [=Unregister the system key press handler=].

					1. Set [=enable keyboard lock=] to be false.

					1. Reset [=reserved key codes=] to be an empty sequence.

			<div></div>

			Note: When a document is closed, the user agent MUST implicitly call
			{{unlock()}} so that the [system key press handler=] (if any) is
			unregistered.

		</div><!-- unlock() -->

		</div><!-- dfn-for Keyboard -->
		
<h2 id="handling-events">Handling Keyboard Key Presses</h2>

	<h3 id="key-press-handler">System Key Press Handler</h3>
		A <dfn>system key press handler</dfn> is an platform-specific handler
		that can be used to filter keys at the platform level. Since
		Keyboard Lock feature is intended to provide access to key
		presses that are not normally made available to the browser (for
		example, Cmd/Alt-Tab), most platforms will require a special handler
		to be set up.

		The [=system key press handler=] must have the following properties:

		* It must process key presses before any user agent keyboard shortcuts
			are handled.
		* Wherever possible, it should process key presses before any system
			keyboard shortcuts are processed.

		<h4 id="registering">Registering</h4>

			To <dfn>register a system key press handler</dfn>, the user agent
			will need to follow the platform-specific steps to add a low-level
			hook that will be called whenever the platform begins to process a
			new key press.

			The exact process for adding a [=system key press handler=] varies
			from platform to platform. For examples of how to register a
			low-level hook to process key presses on common platforms, see
			[[LowLevelKeyboardProc]] for Windows, [[QuartzEventServices]] for
			Mac OS X and [[GrabKeyboard]] for X Windows.

			Note: If the user agent already has a key press handler registered
			for another purpose, then it can optionally extend that handler to
			support the Keyboard Lock feature (assuming it meets the
			requirements mentioned above).

		<h4 id="unregistering">Unregistering</h4>

			To <dfn>unregister the system key press handler</dfn>, the user
			agent will need to follow the platform-specific steps to remove the
			(previously added) low-level hook for processing new key press.

			As with registering system key press handlers, the process for
			unregistering system key press handlers is also platform-specific.
			See the references listed in [[#registering]] for more details and
			examples.

	<h3 id="handling-keyboard-events">Handling Keyboard Events</h3>

		In response to the user pressing a key, if a
		[=system key press handler=] has been
		<a lt="register a system key press handler">registered</a>,
		it should run the following steps:

		1. Let |isFullscreen| be set to true if the [=fullscreen element=] of the
			[=currently focused area of a top-level browsing context=] is non-null
			(see [[Fullscreen]]).

			Note: The [=fullscreen element=] would not have focus, for example, if
			there was a system dialog being displayed with focus.

		1. If |isFullscreen| and [=enable keyboard lock=] are all set
			to true, then run the following substeps:

			1. Let |keyEvent| be the key event for the new key press.

			1. Let |code| be the value of the {{KeyboardEvent/code}} attribute of |keyEvent|.

			1. If [=reserved key codes=] is empty or if |code| is listed in
				[=reserved key codes=], then run the following substeps:

				1. If |code| is equal to "Escape", then run the following
					substeps:

					1. Optionally overlay a message on the screen telling the
						user that they can Hold the Escape key to exit from
						fullscreen.

					1. If the key is held for 2 seconds, then exit from the
						keyboard handler and pass the key on to the user agent
						for normal processing (which will exit fullscreen
						(and pointer lock, if active)).

				1. Dispatch |keyEvent| directly to the fullsceen document or element,
					bypassing any normal user agent processing.

			1. Else, handle the key event as it normally would be handled,
				either by dispatching a key event or performing the
				usual keyboard shortcut action.

		Note: This API operates on a "best effort" basis.
		It is not required that a conforming implementation be able to
		override the OS default behaviour for every possible key combination.
		Specifically, most platforms have a “secure attention sequence” (e.g.,
		Ctrl-Alt-Del on Windows) that applications cannot override; this
		specification does not supersede that.

		Note: When implementing this API, user agents should take care not to change
		the order in which keyboard events are dispatched to the page. Keys that are
		included in the set of [=reserved key codes=] must be dispatched in the same
		relative order that they would have been sent had they not been included in the
		set.

<h2 id="interactions">Intergration With Other Web Platform APIs</h2>

	[[Fullscreen]] and [[PointerLock]] are APIs that allow the page to temporarily take
	control of part of the user's experience (screen and mouse cursor, respectively).
	Because of the concern for abuse of these features, they provide an "escape" or
	"unlock" gesture that the user can rely on to exit those features. By default,
	this gesture is pressing the Escape key, which is one of the keys that can be
	captured by this API.

	<h3 id="escape-key">Special Considerations with the Escape Key</h3>

		Because of the special actions associated with the Escape key, when the {{lock()}}
		request includes the Escape key, the user agent may need to make additional
		changes to the UX to account for the changed behavior.

		For example, if the user agent shows a user message "Press ESC to exit fullscreen"
		when Javascript-initiated fullscreen is activated, then that message will need to
		be updated when keyboard lock is in effect to read "Press and hold ESC to exit
		fullscreen".

		If keyboard lock is activated after fullscreen is already in effect, then the user
		my see multiple messages about how to exit fullscreen.
		For this reason, we recommend that developers call {{lock()}} before they enter
		fullscreen:
	
		<pre>
			navigator.keyboard.lock();
			document.documentElement.requestFullscreen();
		</pre>

		A similar concern with multiple user messages exists when exiting keyboard lock and
		fullscreen, so it is recommended to call them in the reverse order:
	
		<pre>
			document.exitFullscreen();
			navigator.keyboard.unlock();
		</pre>

		In general, developers should only include the Escape key in the set of locked
		keys if they actually have need for that key. And it is recommended that, if the
		Escape key is locked, the developer should maintain its primary meaning of
		allowing the user to exit their current state.

	<h3 id="fullscreen-considerations">Fullscreen Considerations</h3>

		There are two different types of fullscreen available in modern user agents:
		JavaScript-initiated fullscreen (via the [[Fullscreen]] API) and
		user-initiated fullscreen (when the user enters fullscreen using a keyboard
		shortcut). The user-initiated fullscreen is often referred to as "F11"
		fullscreen since that is a common key shortcut used to enter and exit
		fullscreen mode.

		F11 fullscreen and JavaScript (JS) fullscreen do not behave the same way.
		When a user enters F11 fullscreen, they can only exit it via the same
		keyboard shortcut that they used to enter it -- the exitFullscreen()
		function will not work in this case. In addition, fullscreen events that are
		normally fired for JS fullscreen are not sent for F11 fullscreen.

		Because of these differences (and because there is no standard shortcut
		for F11 fullscreen), the Keyboard Lock API is only valid when the
		a JavaScript-initiated fullscreen is active. During F11 fullscreen, no
		Keyboard Lock processing of keyboard events will take place.

	<h2 id="pointerlock-considerations">Pointer Lock Considerations</h2>

		Other than the UX changes noted earlier, there are no changes to the operation
		of Pointer Lock.

		When Pointer Lock is enabled outside of fullscreen, then Keyboard Lock cannot
		be enabled.
		
		When Pointer Lock, Keyboard Lock and Fullscreen are all enabled, then the behavior
		is unchanged unless Keyboard Lock includes the Escape key. In that case, the
		only chages are to the UX (as noted above).

<h2 id="mobile">Mobile Device Considerations</h2>

	Since this is a keyboard-focused API and mobile devices do not commonly
	have physical keyboards, this API will not typically be present or
	supported on mobile devices.

	However, mobile devices may choose to support this API if it makes sense to
	do so when a physical keyboard is connected.

<h2 id="security">Security Considerations</h2>

	One concern with this API is that it could be used to grab all of the keys
	and (in conjunction with the [[Fullscreen]] and [[PointerLock]] API) prevent
	the user from exiting the web page. 
	
	To prevent this, the user agent MUST provide a way for the user to exit from
	keyboard lock even if all of the keys are requested by the API.
	
	This specification requires support for allowing a long (more than 2 second) Escape
	key press to trigger an exit from Keyboard Lock. In addition, user agents may choose
	to also provide alternate ways to exit Keyboard Lock.
	
<h2 id="privacy">Privacy Considerations</h2>

	Not applicable. This API does not use or reveal any personal information
	about the current user.

<h2 id="acknowledgements-contributors">Acknowledgements</h2>

	Thanks to the following people for the discussions that lead
	to the creation of this proposal:

	Jon Dahlke (Google),
	Joe Downing (Google)
